#ifndef _POINT_
#define _POINT_

#include <cmath>

/*
	a 2 dimensional physics vector full of overloaded operators
*/
template<typename T>
class Point2D {
public:
	T x;
	T y;
	
	Point2D(T X = 0, T Y = 0): x(X), y(Y) {}
	~Point2D() {}
	
	bool operator<(const Point2D& o) const {
		if(this->x == o.x)	return this->y < o.y;
		return this->x < o.x;
	}
	inline void normalize() {
		(*this) /= magnitude();
	}
	inline Point2D normalized() {
		return (*this) / magnitude();
	}
	inline T magnitude() const {
		return std::sqrt(magnitudeSquared());
	}
	inline T magnitudeSquared() const {
		return (*this) | (*this);
	}
	inline bool operator== (const Point2D& o) const {
		return x == o.x && y == o.y;
	}
	inline bool operator!= (const Point2D& o) const {
		return x != o.x || y != o.y;
	}
	inline Point2D operator *(const T &o) const {
		return Point2D(x * o, y * o);
	}
	inline Point2D operator /(const T &o) const {
		return Point2D(x / o, y / o);
	}
	inline Point2D operator +(const Point2D &o) const {
		return Point2D(x + o.x, y + o.y);
	}
	inline Point2D operator -(const Point2D &o) const {
		return Point2D(x - o.x, y - o.y);
	}
	inline Point2D operator *(const Point2D &o) const {
		return Point2D(x * o.x, y * o.y);
	}
	inline Point2D operator /(const Point2D &o) const {
		return Point2D(x / o.x, y / o.y);
	}
	inline T operator | (const Point2D& o) const {
		return x*o.x + y*o.y;
	}
	inline void operator *=(const T &n) {
		x *= n;	y *= n;
	}
	inline void operator /=(const T &n) {
		x /= n;	y /= n;
	}
	inline void operator +=(const Point2D &v) {
		x += v.x;	y += v.y;
	}
	inline void operator -=(const Point2D &v) {
		x -= v.x;	y -= v.y;
	}
};

/*
	A 3D physics vector that expands upon the 2D version
*/
template <typename T>
class Point3D: public Point2D<T> {
public:
	T z;
	Point3D(const Point2D<T> &o): Point2D<T>(o.x,o.y), z(0)	{}
	Point3D(T X = 0, T Y = 0, T Z = 0): Point2D<T>(X,Y), z(Z)	{}
	Point3D operator=(const Point2D<T>& o)	{	this->x = o.x;	this->y = o.y;	return *this;	}
	bool operator<(const Point3D& o) const	{	return this->x < o.x || this->y < o.y || z < o.z;	}
	
	inline T magnitude() const {
		T magSq = magnitudeSquared();
		if(magSq == 1)	return 1;
		return std::sqrt(magSq);
	}
	inline T magnitudeSquared() const {
		return (*this) | (*this);
	}
	inline void normalize() {
		(*this) /= magnitude();
	}
	inline Point3D normalized() const {
		return (*this) / magnitude();
	}
	inline bool operator== (const Point3D& o) {
		return this->Point2D<T>::operator==(o) && z == o.z;
	}
	inline bool operator!= (const Point3D& o) {
		return this->Point2D<T>::operator!=(o) || z != o.z;
	}
	inline Point3D operator *(const T &o) const {
		return Point3D(this->x * o, this->y * o, z * o);
	}
	inline Point3D operator /(const T &o) const {
		return Point3D(this->x / o, this->y / o, z / o);
	}
	inline Point3D operator +(const Point3D &o) const{
		return Point3D(this->x + o.x, this->y + o.y, z + o.z);
	}
	inline Point3D operator -(const Point3D &o) const{
		return Point3D(this->x - o.x, this->y - o.y, z - o.z);
	}
	inline Point3D operator *(const Point3D &o) const {
		return Point3D(this->x * o.x, this->y * o.y, z * o.z);
	}
	inline Point3D operator /(const Point3D &o) const {
		return Point3D(this->x / o.x, this->y / o.y, z / o.z);
	}
	inline T operator | (const Point3D& o) const {
		return Point2D<T>::operator|(o) + z*o.z;
	}
	inline Point3D operator ^(const Point3D& o) const {
		return Point3D(this->y*o.z - this->z*o.y, this->z*o.x - this->x*o.z, this->x*o.y - this->y*o.x);
	}
	inline void operator *=(const T &n) {
		this->x *= n;	this->y *= n;	z *= n;
	}
	inline void operator /=(const T &n) {
		this->x /= n;	this->y /= n;	z /= n;
	}
	inline void operator +=(const Point3D &v) {
		this->x += v.x;	this->y += v.y;	z += v.z;
	}
	inline void operator -=(const Point3D &v) {
		this->x -= v.x;	this->y -= v.y;	z -= v.z;
	}	
};

//Common types used with these classes get a typedef
typedef Point3D<double>	fVec3D;
typedef Point2D<double>	fVec2D;
typedef Point3D<long>	lVec3D;
typedef Point2D<long>	lVec2D;

//Quick and dirty conversion functions between the two
fVec3D convert(const lVec3D& o);
lVec3D convert(const fVec3D& o);

#endif
